/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.objer;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

import org.zhiqim.git.Git;
import org.zhiqim.git.GitConstants;
import org.zhiqim.git.GitZiper;
import org.zhiqim.git.objer.model.Commit;
import org.zhiqim.git.objer.model.Tag;
import org.zhiqim.git.objer.objectid.ObjectId;

import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Files;
import org.zhiqim.kernel.util.Streams;
import org.zhiqim.kernel.util.codes.SHA1;
import org.zhiqim.kernel.util.streams.DigestOutputStream;

/**
 * 松散对象编写器
 *
 * @version v1.0.0 @author zouzhigang 2017-4-15 新建与整理
 */
public class ObjectWriter implements GitConstants
{
    private final Git git;
    private final byte[] buf;
    private final MessageDigest md;

    public ObjectWriter(Git git)
    {
        this.git = git;
        this.buf = new byte[8192];
        this.md = SHA1.getInstance();
    }

    /*******************************************************************************************/
    //写入提交信息
    /*******************************************************************************************/
    
    public ObjectId writeCommit(Commit c) throws IOException
    {
        return writeObject(TYPE_COMMIT_BYTES, c.build());
    }
    
    /*******************************************************************************************/
    //写入标签信息
    /*******************************************************************************************/
    
    public ObjectId writeTag(Tag c) throws IOException
    {
        return writeObject(TYPE_TAG_BYTES, c.build());
    }
    
    /*******************************************************************************************/
    //写入对象信息
    /*******************************************************************************************/
    
    public ObjectId writeBlob(File file) throws IOException
    {
        try(FileInputStream is = new FileInputStream(file))
        {
            return writeBlob(file.length(), is);
        }
    }
    
    public ObjectId writeBlob(byte[] b) throws IOException
    {
        return writeObject(TYPE_BLOB_BYTES, b);
    }

    private ObjectId writeBlob(long len, InputStream is) throws IOException
    {
        return writeObject(TYPE_BLOB_BYTES, len, is);
    }
    
    /** 计算对象编码 */
    public ObjectId computeBlobSha1(long len, InputStream input) throws IOException
    {
        md.reset();
        md.update(TYPE_BLOB_BYTES);
        md.update((byte)' ');
        md.update(Bytes.toBytesASCII(len));
        md.update((byte)0);

        int count = 0;
        while ((count = input.read(buf)) != -1)
        {
            md.update(buf, 0, count);
        }

        return ObjectId.parse(md.digest());
    }
    
    /*******************************************************************************************/
    //写入树信息
    /*******************************************************************************************/
    
    public ObjectId writeTree(byte[] b) throws IOException
    {
        return writeObject(TYPE_TREE_BYTES, b);
    }

    /*******************************************************************************************/
    //写入对象
    /*******************************************************************************************/

    private ObjectId writeObject(byte[] type, byte[] buf) throws IOException
    {
        return writeObject(type, buf.length, new ByteArrayInputStream(buf));
    }
    
    private ObjectId writeObject(byte[] type, long len, InputStream is) throws IOException
    {
        File tmpFile = File.createTempFile("noz", null, git.getObjectsDirectory());
        FileOutputStream fout = new FileOutputStream(tmpFile);

        md.reset();
        Deflater def = GitZiper.deflater(git.getConfig().getCompression());
        
        DeflaterOutputStream dout = new DeflaterOutputStream(fout, def);
        DigestOutputStream out = new DigestOutputStream(dout, md);
        
        ObjectId id = null;
        try
        {
            out.write(type);
            out.write((byte)' ');
            out.write(Bytes.toBytesASCII(len));
            out.write((byte)0);

            Streams.putBytes(is, out);

            out.close();
            tmpFile.setReadOnly();

            id = ObjectId.parse(out.digest());
        }
        catch (IOException e)
        {//异常关闭压缩流并删除临时文件，再抛出异常
            try{dout.close();}finally{tmpFile.delete();}
            throw e;
        }
        finally
        {
            GitZiper.close(def);
        }

        if (git.hasObject(id))
        {//已存在删除临时文件
            tmpFile.delete();
        }
        else
        {//重命名为标准松散式对象文件
            File file = git.fileForUnpacked(id);
            Files.mkDirectory(file.getParentFile());
            if (!tmpFile.renameTo(file))
            {
                if (!git.getObjer().hasUnpackedObject(id))
                {//重命名失败，删除临时文件，并抛出异常
                    tmpFile.delete();
                    throw Asserts.exception("写入对象时，由临时文件重命名为正式文件时失败：" + file);
                }
            }
        }

        return id;
    }
}
